{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "af6c7afe-1037-41ab-98e4-494692e47402",
   "metadata": {
    "id": "af6c7afe-1037-41ab-98e4-494692e47402"
   },
   "source": [
    "# Chatbot with message summarization & external DB memory\n",
    "\n",
    "## Review\n",
    "\n",
    "We've covered how to customize graph state schema and reducer.\n",
    "\n",
    "We've also shown a number of tricks for trimming or filtering messages in graph state.\n",
    "\n",
    "We've used these concepts in a Chatbot with memory that produces a running summary of the conversation.\n",
    "\n",
    "## Goals\n",
    "\n",
    "But, what if we want our Chatbot to have memory that persists indefinitely?\n",
    "\n",
    "Now, we'll introduce some more advanced checkpointers that support external databases.\n",
    "\n",
    "Here, we'll show how to use [Sqlite as a checkpointer](https://langchain-ai.github.io/langgraph/concepts/low_level/#checkpointer), but other checkpointers, such as [Postgres](https://langchain-ai.github.io/langgraph/how-tos/persistence_postgres/) are available!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b38746af",
   "metadata": {},
   "source": [
    "## Use Cases for Database File Management:\n",
    "1. Fetching a Database File from a GitHub URL\n",
    "2. Uploading a Database File from Your Local System\n",
    "3. Accessing a Database File from Google Drive"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "85ed78d9-6ca2-45ac-96a9-52e341ec519d",
   "metadata": {
    "id": "85ed78d9-6ca2-45ac-96a9-52e341ec519d"
   },
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install --quiet -U langgraph-checkpoint-sqlite langchain_core langgraph langchain_google_genai"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "2e10c4d4",
   "metadata": {
    "id": "2e10c4d4"
   },
   "outputs": [],
   "source": [
    "from google.colab import userdata\n",
    "GEMINI_API_KEY = userdata.get('GEMINI_API_KEY')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "6a002315",
   "metadata": {
    "id": "6a002315"
   },
   "outputs": [],
   "source": [
    "import os\n",
    "os.environ[\"LANGCHAIN_API_KEY\"] = userdata.get('LANGCHAIN_API_KEY')\n",
    "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
    "os.environ[\"LANGCHAIN_PROJECT\"] = \"langchain-academy\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b40d25c0-e9b5-4854-bf07-3cc3ff07122e",
   "metadata": {
    "id": "b40d25c0-e9b5-4854-bf07-3cc3ff07122e"
   },
   "source": [
    "## Sqlite\n",
    "\n",
    "A good starting point here is the [SqliteSaver checkpointer](https://langchain-ai.github.io/langgraph/concepts/low_level/#checkpointer).\n",
    "\n",
    "Sqlite is a [small, fast, highly popular](https://x.com/karpathy/status/1819490455664685297) SQL database.\n",
    "\n",
    "If we supply `\":memory:\"` it creates an in-memory Sqlite database."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "fae15402-17ae-4e89-8ecf-4c89e08b22fe",
   "metadata": {
    "id": "fae15402-17ae-4e89-8ecf-4c89e08b22fe"
   },
   "outputs": [],
   "source": [
    "import sqlite3\n",
    "# In memory - temporary database is created, not on-disk database\n",
    "conn = sqlite3.connect(\":memory:\", check_same_thread = False) #check_same_thread = False | allowing multiple threads to share and use the same SQLite database connection. By default it is True"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c2bf53ec-6d4a-42ce-8183-344795eed403",
   "metadata": {
    "id": "c2bf53ec-6d4a-42ce-8183-344795eed403"
   },
   "source": [
    "## Default Behavior:\n",
    "\n",
    "When you run the code for the first time, it will automatically fetch a new example.db file for you.\n",
    "But, if we supply a db path, then it will create a database for us!:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "58339167-920c-4994-a0a7-0a9c5d4f7cf7",
   "metadata": {
    "id": "58339167-920c-4994-a0a7-0a9c5d4f7cf7"
   },
   "outputs": [],
   "source": [
    "import sqlite3\n",
    "# pull file if it doesn't exist and connect to local db\n",
    "\n",
    "!mkdir -p state_db && [ ! -f state_db/example.db ] && wget -P state_db https://github.com/langchain-ai/langchain-academy/raw/main/module-2/state_db/example.db\n",
    "\n",
    "db_path = \"state_db/example.db\"\n",
    "conn = sqlite3.connect(db_path, check_same_thread=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d-3TxBOlSQsc",
   "metadata": {
    "id": "d-3TxBOlSQsc"
   },
   "source": [
    "## Mount with Google Drive\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "5qxV1SIdSN3q",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "5qxV1SIdSN3q",
    "outputId": "8cccdd5d-957d-4415-d79f-f62789d89960"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Mounted at /content/drive\n"
     ]
    }
   ],
   "source": [
    "from google.colab import drive\n",
    "drive.mount('/content/drive')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "r3ndkSuyVQWE",
   "metadata": {
    "id": "r3ndkSuyVQWE"
   },
   "source": [
    "connection is establish"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "njrumCAhRjwE",
   "metadata": {
    "id": "njrumCAhRjwE"
   },
   "outputs": [],
   "source": [
    "import sqlite3\n",
    "file_path = '/content/drive/My Drive/state_db/example.db'\n",
    "conn = sqlite3.connect(file_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "QwfuVtOi3FVl",
   "metadata": {
    "id": "QwfuVtOi3FVl"
   },
   "source": [
    "### When you want to pass your DB file:\n",
    "You need example.db as well as example.db-wal file to retrieve your previous data.\n",
    "\n",
    "Sqlite uses these 3 files:\n",
    "\n",
    "\n",
    "1.   example.db\n",
    "2.   example.db-wal\n",
    "3.   example.db-shm\n",
    "\n",
    "### How SQLite Uses These Files\n",
    "1. example.db:\n",
    "  This is the main database file where committed data is stored.\n",
    "\n",
    "2. example.db-wal:\n",
    "  If SQLite is in WAL mode, this file temporarily holds changes that\n",
    "  have not been checkpointed (written to example.db).\n",
    "  SQLite automatically reads from this file when you connect to the database.\n",
    "\n",
    "3. example.db-shm:\n",
    "  This file is used for shared memory when multiple connections are accessing the same database.\n",
    "  It is a temporary file and does not affect persistence.\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ExhbqtDfH-6D",
   "metadata": {
    "id": "ExhbqtDfH-6D"
   },
   "source": [
    "### Why you need to save example.db-wal file along with example.db file?\n",
    "\n",
    "1. **Uncommitted Changes Are Stored in `example.db-wal`**:\n",
    "   - In WAL mode, SQLite writes all changes to the `example.db-wal` file instead of directly modifying the main `example.db` file.\n",
    "   - These changes are only merged (checkpointed) into `example.db` when a checkpoint operation occurs.\n",
    "\n",
    "   If you only copy or use the `example.db` file without the `example.db-wal`, **any uncommitted changes in the WAL file will be lost.**\n",
    "\n",
    "2. **Ensures Data Consistency**:\n",
    "   - If you transfer the database (`example.db`) to another system without the `example.db-wal`, the database may appear incomplete or inconsistent if there are pending writes stored in the WAL file.\n",
    "\n",
    "3. **Concurrent Access**:\n",
    "   - The WAL file allows multiple processes to read from the database while another process writes to it, without blocking each other. If this feature is in use, the `example.db-wal` file is essential for proper functionality.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4sPiAKFq3EQT",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 73
    },
    "id": "4sPiAKFq3EQT",
    "outputId": "d6918f65-f896-4c0b-d954-4d3ebf0bfb84"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "     <input type=\"file\" id=\"files-a2b6d09c-b4b8-43eb-aa82-fb0458c2c3be\" name=\"files[]\" multiple disabled\n",
       "        style=\"border:none\" />\n",
       "     <output id=\"result-a2b6d09c-b4b8-43eb-aa82-fb0458c2c3be\">\n",
       "      Upload widget is only available when the cell has been executed in the\n",
       "      current browser session. Please rerun this cell to enable.\n",
       "      </output>\n",
       "      <script>// Copyright 2017 Google LLC\n",
       "//\n",
       "// Licensed under the Apache License, Version 2.0 (the \"License\");\n",
       "// you may not use this file except in compliance with the License.\n",
       "// You may obtain a copy of the License at\n",
       "//\n",
       "//      http://www.apache.org/licenses/LICENSE-2.0\n",
       "//\n",
       "// Unless required by applicable law or agreed to in writing, software\n",
       "// distributed under the License is distributed on an \"AS IS\" BASIS,\n",
       "// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
       "// See the License for the specific language governing permissions and\n",
       "// limitations under the License.\n",
       "\n",
       "/**\n",
       " * @fileoverview Helpers for google.colab Python module.\n",
       " */\n",
       "(function(scope) {\n",
       "function span(text, styleAttributes = {}) {\n",
       "  const element = document.createElement('span');\n",
       "  element.textContent = text;\n",
       "  for (const key of Object.keys(styleAttributes)) {\n",
       "    element.style[key] = styleAttributes[key];\n",
       "  }\n",
       "  return element;\n",
       "}\n",
       "\n",
       "// Max number of bytes which will be uploaded at a time.\n",
       "const MAX_PAYLOAD_SIZE = 100 * 1024;\n",
       "\n",
       "function _uploadFiles(inputId, outputId) {\n",
       "  const steps = uploadFilesStep(inputId, outputId);\n",
       "  const outputElement = document.getElementById(outputId);\n",
       "  // Cache steps on the outputElement to make it available for the next call\n",
       "  // to uploadFilesContinue from Python.\n",
       "  outputElement.steps = steps;\n",
       "\n",
       "  return _uploadFilesContinue(outputId);\n",
       "}\n",
       "\n",
       "// This is roughly an async generator (not supported in the browser yet),\n",
       "// where there are multiple asynchronous steps and the Python side is going\n",
       "// to poll for completion of each step.\n",
       "// This uses a Promise to block the python side on completion of each step,\n",
       "// then passes the result of the previous step as the input to the next step.\n",
       "function _uploadFilesContinue(outputId) {\n",
       "  const outputElement = document.getElementById(outputId);\n",
       "  const steps = outputElement.steps;\n",
       "\n",
       "  const next = steps.next(outputElement.lastPromiseValue);\n",
       "  return Promise.resolve(next.value.promise).then((value) => {\n",
       "    // Cache the last promise value to make it available to the next\n",
       "    // step of the generator.\n",
       "    outputElement.lastPromiseValue = value;\n",
       "    return next.value.response;\n",
       "  });\n",
       "}\n",
       "\n",
       "/**\n",
       " * Generator function which is called between each async step of the upload\n",
       " * process.\n",
       " * @param {string} inputId Element ID of the input file picker element.\n",
       " * @param {string} outputId Element ID of the output display.\n",
       " * @return {!Iterable<!Object>} Iterable of next steps.\n",
       " */\n",
       "function* uploadFilesStep(inputId, outputId) {\n",
       "  const inputElement = document.getElementById(inputId);\n",
       "  inputElement.disabled = false;\n",
       "\n",
       "  const outputElement = document.getElementById(outputId);\n",
       "  outputElement.innerHTML = '';\n",
       "\n",
       "  const pickedPromise = new Promise((resolve) => {\n",
       "    inputElement.addEventListener('change', (e) => {\n",
       "      resolve(e.target.files);\n",
       "    });\n",
       "  });\n",
       "\n",
       "  const cancel = document.createElement('button');\n",
       "  inputElement.parentElement.appendChild(cancel);\n",
       "  cancel.textContent = 'Cancel upload';\n",
       "  const cancelPromise = new Promise((resolve) => {\n",
       "    cancel.onclick = () => {\n",
       "      resolve(null);\n",
       "    };\n",
       "  });\n",
       "\n",
       "  // Wait for the user to pick the files.\n",
       "  const files = yield {\n",
       "    promise: Promise.race([pickedPromise, cancelPromise]),\n",
       "    response: {\n",
       "      action: 'starting',\n",
       "    }\n",
       "  };\n",
       "\n",
       "  cancel.remove();\n",
       "\n",
       "  // Disable the input element since further picks are not allowed.\n",
       "  inputElement.disabled = true;\n",
       "\n",
       "  if (!files) {\n",
       "    return {\n",
       "      response: {\n",
       "        action: 'complete',\n",
       "      }\n",
       "    };\n",
       "  }\n",
       "\n",
       "  for (const file of files) {\n",
       "    const li = document.createElement('li');\n",
       "    li.append(span(file.name, {fontWeight: 'bold'}));\n",
       "    li.append(span(\n",
       "        `(${file.type || 'n/a'}) - ${file.size} bytes, ` +\n",
       "        `last modified: ${\n",
       "            file.lastModifiedDate ? file.lastModifiedDate.toLocaleDateString() :\n",
       "                                    'n/a'} - `));\n",
       "    const percent = span('0% done');\n",
       "    li.appendChild(percent);\n",
       "\n",
       "    outputElement.appendChild(li);\n",
       "\n",
       "    const fileDataPromise = new Promise((resolve) => {\n",
       "      const reader = new FileReader();\n",
       "      reader.onload = (e) => {\n",
       "        resolve(e.target.result);\n",
       "      };\n",
       "      reader.readAsArrayBuffer(file);\n",
       "    });\n",
       "    // Wait for the data to be ready.\n",
       "    let fileData = yield {\n",
       "      promise: fileDataPromise,\n",
       "      response: {\n",
       "        action: 'continue',\n",
       "      }\n",
       "    };\n",
       "\n",
       "    // Use a chunked sending to avoid message size limits. See b/62115660.\n",
       "    let position = 0;\n",
       "    do {\n",
       "      const length = Math.min(fileData.byteLength - position, MAX_PAYLOAD_SIZE);\n",
       "      const chunk = new Uint8Array(fileData, position, length);\n",
       "      position += length;\n",
       "\n",
       "      const base64 = btoa(String.fromCharCode.apply(null, chunk));\n",
       "      yield {\n",
       "        response: {\n",
       "          action: 'append',\n",
       "          file: file.name,\n",
       "          data: base64,\n",
       "        },\n",
       "      };\n",
       "\n",
       "      let percentDone = fileData.byteLength === 0 ?\n",
       "          100 :\n",
       "          Math.round((position / fileData.byteLength) * 100);\n",
       "      percent.textContent = `${percentDone}% done`;\n",
       "\n",
       "    } while (position < fileData.byteLength);\n",
       "  }\n",
       "\n",
       "  // All done.\n",
       "  yield {\n",
       "    response: {\n",
       "      action: 'complete',\n",
       "    }\n",
       "  };\n",
       "}\n",
       "\n",
       "scope.google = scope.google || {};\n",
       "scope.google.colab = scope.google.colab || {};\n",
       "scope.google.colab._files = {\n",
       "  _uploadFiles,\n",
       "  _uploadFilesContinue,\n",
       "};\n",
       "})(self);\n",
       "</script> "
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Saving example.db to example.db\n"
     ]
    }
   ],
   "source": [
    "#Follow step if you already have previous database file\n",
    "#uplaod example.db file from your system\n",
    "from google.colab import files\n",
    "uploaded = files.upload()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5YzkFJceChRK",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 73
    },
    "id": "5YzkFJceChRK",
    "outputId": "83ca0846-e63a-4a3c-c616-c117d49af6ee"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "     <input type=\"file\" id=\"files-2d19fc14-12da-4457-a70f-ad32d1af39c0\" name=\"files[]\" multiple disabled\n",
       "        style=\"border:none\" />\n",
       "     <output id=\"result-2d19fc14-12da-4457-a70f-ad32d1af39c0\">\n",
       "      Upload widget is only available when the cell has been executed in the\n",
       "      current browser session. Please rerun this cell to enable.\n",
       "      </output>\n",
       "      <script>// Copyright 2017 Google LLC\n",
       "//\n",
       "// Licensed under the Apache License, Version 2.0 (the \"License\");\n",
       "// you may not use this file except in compliance with the License.\n",
       "// You may obtain a copy of the License at\n",
       "//\n",
       "//      http://www.apache.org/licenses/LICENSE-2.0\n",
       "//\n",
       "// Unless required by applicable law or agreed to in writing, software\n",
       "// distributed under the License is distributed on an \"AS IS\" BASIS,\n",
       "// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
       "// See the License for the specific language governing permissions and\n",
       "// limitations under the License.\n",
       "\n",
       "/**\n",
       " * @fileoverview Helpers for google.colab Python module.\n",
       " */\n",
       "(function(scope) {\n",
       "function span(text, styleAttributes = {}) {\n",
       "  const element = document.createElement('span');\n",
       "  element.textContent = text;\n",
       "  for (const key of Object.keys(styleAttributes)) {\n",
       "    element.style[key] = styleAttributes[key];\n",
       "  }\n",
       "  return element;\n",
       "}\n",
       "\n",
       "// Max number of bytes which will be uploaded at a time.\n",
       "const MAX_PAYLOAD_SIZE = 100 * 1024;\n",
       "\n",
       "function _uploadFiles(inputId, outputId) {\n",
       "  const steps = uploadFilesStep(inputId, outputId);\n",
       "  const outputElement = document.getElementById(outputId);\n",
       "  // Cache steps on the outputElement to make it available for the next call\n",
       "  // to uploadFilesContinue from Python.\n",
       "  outputElement.steps = steps;\n",
       "\n",
       "  return _uploadFilesContinue(outputId);\n",
       "}\n",
       "\n",
       "// This is roughly an async generator (not supported in the browser yet),\n",
       "// where there are multiple asynchronous steps and the Python side is going\n",
       "// to poll for completion of each step.\n",
       "// This uses a Promise to block the python side on completion of each step,\n",
       "// then passes the result of the previous step as the input to the next step.\n",
       "function _uploadFilesContinue(outputId) {\n",
       "  const outputElement = document.getElementById(outputId);\n",
       "  const steps = outputElement.steps;\n",
       "\n",
       "  const next = steps.next(outputElement.lastPromiseValue);\n",
       "  return Promise.resolve(next.value.promise).then((value) => {\n",
       "    // Cache the last promise value to make it available to the next\n",
       "    // step of the generator.\n",
       "    outputElement.lastPromiseValue = value;\n",
       "    return next.value.response;\n",
       "  });\n",
       "}\n",
       "\n",
       "/**\n",
       " * Generator function which is called between each async step of the upload\n",
       " * process.\n",
       " * @param {string} inputId Element ID of the input file picker element.\n",
       " * @param {string} outputId Element ID of the output display.\n",
       " * @return {!Iterable<!Object>} Iterable of next steps.\n",
       " */\n",
       "function* uploadFilesStep(inputId, outputId) {\n",
       "  const inputElement = document.getElementById(inputId);\n",
       "  inputElement.disabled = false;\n",
       "\n",
       "  const outputElement = document.getElementById(outputId);\n",
       "  outputElement.innerHTML = '';\n",
       "\n",
       "  const pickedPromise = new Promise((resolve) => {\n",
       "    inputElement.addEventListener('change', (e) => {\n",
       "      resolve(e.target.files);\n",
       "    });\n",
       "  });\n",
       "\n",
       "  const cancel = document.createElement('button');\n",
       "  inputElement.parentElement.appendChild(cancel);\n",
       "  cancel.textContent = 'Cancel upload';\n",
       "  const cancelPromise = new Promise((resolve) => {\n",
       "    cancel.onclick = () => {\n",
       "      resolve(null);\n",
       "    };\n",
       "  });\n",
       "\n",
       "  // Wait for the user to pick the files.\n",
       "  const files = yield {\n",
       "    promise: Promise.race([pickedPromise, cancelPromise]),\n",
       "    response: {\n",
       "      action: 'starting',\n",
       "    }\n",
       "  };\n",
       "\n",
       "  cancel.remove();\n",
       "\n",
       "  // Disable the input element since further picks are not allowed.\n",
       "  inputElement.disabled = true;\n",
       "\n",
       "  if (!files) {\n",
       "    return {\n",
       "      response: {\n",
       "        action: 'complete',\n",
       "      }\n",
       "    };\n",
       "  }\n",
       "\n",
       "  for (const file of files) {\n",
       "    const li = document.createElement('li');\n",
       "    li.append(span(file.name, {fontWeight: 'bold'}));\n",
       "    li.append(span(\n",
       "        `(${file.type || 'n/a'}) - ${file.size} bytes, ` +\n",
       "        `last modified: ${\n",
       "            file.lastModifiedDate ? file.lastModifiedDate.toLocaleDateString() :\n",
       "                                    'n/a'} - `));\n",
       "    const percent = span('0% done');\n",
       "    li.appendChild(percent);\n",
       "\n",
       "    outputElement.appendChild(li);\n",
       "\n",
       "    const fileDataPromise = new Promise((resolve) => {\n",
       "      const reader = new FileReader();\n",
       "      reader.onload = (e) => {\n",
       "        resolve(e.target.result);\n",
       "      };\n",
       "      reader.readAsArrayBuffer(file);\n",
       "    });\n",
       "    // Wait for the data to be ready.\n",
       "    let fileData = yield {\n",
       "      promise: fileDataPromise,\n",
       "      response: {\n",
       "        action: 'continue',\n",
       "      }\n",
       "    };\n",
       "\n",
       "    // Use a chunked sending to avoid message size limits. See b/62115660.\n",
       "    let position = 0;\n",
       "    do {\n",
       "      const length = Math.min(fileData.byteLength - position, MAX_PAYLOAD_SIZE);\n",
       "      const chunk = new Uint8Array(fileData, position, length);\n",
       "      position += length;\n",
       "\n",
       "      const base64 = btoa(String.fromCharCode.apply(null, chunk));\n",
       "      yield {\n",
       "        response: {\n",
       "          action: 'append',\n",
       "          file: file.name,\n",
       "          data: base64,\n",
       "        },\n",
       "      };\n",
       "\n",
       "      let percentDone = fileData.byteLength === 0 ?\n",
       "          100 :\n",
       "          Math.round((position / fileData.byteLength) * 100);\n",
       "      percent.textContent = `${percentDone}% done`;\n",
       "\n",
       "    } while (position < fileData.byteLength);\n",
       "  }\n",
       "\n",
       "  // All done.\n",
       "  yield {\n",
       "    response: {\n",
       "      action: 'complete',\n",
       "    }\n",
       "  };\n",
       "}\n",
       "\n",
       "scope.google = scope.google || {};\n",
       "scope.google.colab = scope.google.colab || {};\n",
       "scope.google.colab._files = {\n",
       "  _uploadFiles,\n",
       "  _uploadFilesContinue,\n",
       "};\n",
       "})(self);\n",
       "</script> "
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Saving example.db-wal to example.db-wal\n"
     ]
    }
   ],
   "source": [
    "#Follow step if you already have previous database file\n",
    "#upload example.db-wal file from your system\n",
    "uploaded = files.upload()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6y777yseDojh",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "6y777yseDojh",
    "outputId": "6f744ee6-6ee4-4b14-d4ff-6422d3e0783f"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WAL file moved successfully.\n",
      "Connected to database: state_db/example.db\n"
     ]
    }
   ],
   "source": [
    "#Follow step if you already have previous database file\n",
    "#move both files to state_db folder and establish a connection:\n",
    "\n",
    "import sqlite3\n",
    "import shutil\n",
    "import os\n",
    "\n",
    "# Ensure the state_db directory exists\n",
    "os.makedirs(\"state_db\", exist_ok=True)\n",
    "\n",
    "# Move the uploaded files to the state_db directory\n",
    "shutil.move(\"example.db\", \"state_db/example.db\")\n",
    "if os.path.exists(\"example.db-wal\"):  # Check if WAL file exists\n",
    "    shutil.move(\"example.db-wal\", \"state_db/example.db-wal\")\n",
    "    print(\"WAL file moved successfully.\")\n",
    "else:\n",
    "    print(\"No WAL file found. Proceeding with example.db only.\")\n",
    "\n",
    "# Define the path to the main database file\n",
    "db_path = \"state_db/example.db\"\n",
    "\n",
    "# Connect to the SQLite database\n",
    "try:\n",
    "    conn = sqlite3.connect(db_path, check_same_thread=False)\n",
    "    print(f\"Connected to database: {db_path}\")\n",
    "except sqlite3.OperationalError as e:\n",
    "    print(f\"Error connecting to the database: {e}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "3c7736b6-a750-48f8-a838-8e7616b12250",
   "metadata": {
    "id": "3c7736b6-a750-48f8-a838-8e7616b12250"
   },
   "outputs": [],
   "source": [
    "# Here is our checkpointer\n",
    "from langgraph.checkpoint.sqlite import SqliteSaver\n",
    "memory: SqliteSaver = SqliteSaver(conn)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d8cb629-213f-4b87-965e-19b812c42da1",
   "metadata": {
    "id": "9d8cb629-213f-4b87-965e-19b812c42da1"
   },
   "source": [
    "Let's re-define our chatbot."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "dc414e29-2078-41a0-887c-af1a6a3d72c0",
   "metadata": {
    "id": "dc414e29-2078-41a0-887c-af1a6a3d72c0"
   },
   "outputs": [],
   "source": [
    "from langchain_google_genai import ChatGoogleGenerativeAI\n",
    "from langchain_core.messages import SystemMessage, HumanMessage, RemoveMessage\n",
    "\n",
    "from langgraph.graph import END\n",
    "from langgraph.graph import MessagesState\n",
    "\n",
    "model: ChatGoogleGenerativeAI = ChatGoogleGenerativeAI(model = \"gemini-1.5-flash\", api_key =  GEMINI_API_KEY)\n",
    "\n",
    "class State(MessagesState):\n",
    "    summary: str\n",
    "\n",
    "# Define the logic to call the model\n",
    "def call_model(state: State) -> State:\n",
    "\n",
    "    # Get summary if it exists\n",
    "    summary = state.get(\"summary\", \"\")\n",
    "    print(f\"Using summary: {summary}\")\n",
    "\n",
    "    # If there is summary, then we add it\n",
    "    if summary:\n",
    "\n",
    "        # Add summary to system message\n",
    "        system_message = f\"Summary of conversation earlier: {summary}\"\n",
    "\n",
    "        # Append summary to any newer messages\n",
    "        messages = [SystemMessage(content=system_message)] + state[\"messages\"]\n",
    "\n",
    "    else:\n",
    "        messages = state[\"messages\"]\n",
    "\n",
    "    response = model.invoke(messages)\n",
    "    return {\"messages\": response}\n",
    "\n",
    "def summarize_conversation(state: State) -> State:\n",
    "    print(f\"Messages before summarizing: {len(state['messages'])}\")\n",
    "    # First, we get any existing summary\n",
    "    summary = state.get(\"summary\", \"\")\n",
    "    print(f\"Existing summary: {summary}\")\n",
    "\n",
    "    # Create our summarization prompt\n",
    "    if summary:\n",
    "\n",
    "        # A summary already exists\n",
    "        summary_message = (\n",
    "            f\"This is summary of the conversation to date: {summary}\\n\\n\"\n",
    "            \"Extend the summary by taking into account the new messages above:\"\n",
    "        )\n",
    "\n",
    "    else:\n",
    "        summary_message = \"Create a summary of the conversation above:\"\n",
    "\n",
    "\n",
    "    # Add prompt to our history\n",
    "    messages = state[\"messages\"] + [HumanMessage(content=summary_message)]\n",
    "    response = model.invoke(messages)\n",
    "    # Summarization logic\n",
    "    print(f\"New summary: {response.content}\")\n",
    "\n",
    "    # Delete all but the 2 most recent messages\n",
    "    delete_messages = [RemoveMessage(id=m.id) for m in state[\"messages\"][:-2]]\n",
    "\n",
    "    print(f\"Messages after truncation: {len(delete_messages)}\")\n",
    "    return {\"summary\": response.content, \"messages\": delete_messages}\n",
    "\n",
    "# Determine whether to end or summarize the conversation\n",
    "def should_continue(state: State) -> State:\n",
    "\n",
    "    \"\"\"Return the next node to execute.\"\"\"\n",
    "\n",
    "    messages = state[\"messages\"]\n",
    "    print(f\"Message count: {len(messages)}\")\n",
    "    # If there are more than six messages, then we summarize the conversation\n",
    "    if len(messages) > 6:\n",
    "        return \"summarize_conversation\"\n",
    "\n",
    "    # Otherwise we can just end\n",
    "    return END"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "41c13c0b-a383-4f73-9cc1-63f0eed8f190",
   "metadata": {
    "id": "41c13c0b-a383-4f73-9cc1-63f0eed8f190"
   },
   "source": [
    "Now, we just re-compile with our sqlite checkpointer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "e867fd95-91eb-4ce1-82fc-bb72d611a96d",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 350
    },
    "id": "e867fd95-91eb-4ce1-82fc-bb72d611a96d",
    "outputId": "96b5c23b-c190-4ada-9c73-232f6c7ae9da"
   },
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import Image, display\n",
    "from langgraph.graph import StateGraph, START\n",
    "from langgraph.graph.state import CompiledStateGraph\n",
    "\n",
    "# Define a new graph\n",
    "workflow: StateGraph = StateGraph(State)\n",
    "workflow.add_node(\"conversation\", call_model)\n",
    "workflow.add_node(summarize_conversation)\n",
    "\n",
    "# Set the entrypoint as conversation\n",
    "workflow.add_edge(START, \"conversation\")\n",
    "workflow.add_conditional_edges(\"conversation\", should_continue)\n",
    "workflow.add_edge(\"summarize_conversation\", END)\n",
    "\n",
    "# Compile\n",
    "graph: CompiledStateGraph = workflow.compile(checkpointer=memory)\n",
    "display(Image(graph.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8769db99-3938-45e6-a594-56beb18d6c45",
   "metadata": {
    "id": "8769db99-3938-45e6-a594-56beb18d6c45"
   },
   "source": [
    "Now, we can invoke the graph several times."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "0f4094a0-d240-4be8-903a-7d9f605bdc5c",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "0f4094a0-d240-4be8-903a-7d9f605bdc5c",
    "outputId": "11fd4b7c-e5c9-475b-d88b-26ba31129ffd"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Using summary: Here's a summary of the conversation:\n",
      "\n",
      "Lance introduced himself twice during the conversation. He expressed his fondness for the San Francisco 49ers football team. The AI assistant acknowledged Lance's name each time and showed willingness to discuss the 49ers, offering to talk about various aspects of the team such as their history, current roster, or memorable games. The conversation was brief and somewhat repetitive, with Lance reintroducing himself at the end without directly responding to the AI's questions or prompts about the 49ers.\n",
      "Message count: 8\n",
      "Messages before summarizing: 8\n",
      "Existing summary: Here's a summary of the conversation:\n",
      "\n",
      "Lance introduced himself twice during the conversation. He expressed his fondness for the San Francisco 49ers football team. The AI assistant acknowledged Lance's name each time and showed willingness to discuss the 49ers, offering to talk about various aspects of the team such as their history, current roster, or memorable games. The conversation was brief and somewhat repetitive, with Lance reintroducing himself at the end without directly responding to the AI's questions or prompts about the 49ers.\n",
      "New summary: Here's an extended summary of the conversation:\n",
      "\n",
      "Lance initially introduced himself twice, expressing his fondness for the San Francisco 49ers. The AI assistant acknowledged his name and offered various discussion points related to the 49ers, demonstrating a willingness to engage in a conversation about the team.  However, Lance instead shifted the focus to a broader interest in football itself.  The conversation then transitioned away from the 49ers, with Lance expressing a desire to learn more about football in general. The AI assistant responded by offering several specific areas within the sport that could be discussed, such as rules, positions, famous players, history, strategy, or the business aspects of the game.  The conversation ended with the AI ready to delve into one of these areas, demonstrating adaptability to Lance's changing interests.  The overall interaction showed a willingness from both participants to engage in conversation, but the initial focus on the 49ers was quickly superseded by a broader interest in the sport of football.\n",
      "\n",
      "Messages after truncation: 6\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "Okay, let's talk about football!  To help me focus our conversation, what specifically about football are you interested in?  For example, are you curious about:\n",
      "\n",
      "* **Specific rules of the game?**  (e.g.,  down and distance, penalties, scoring)\n",
      "* **Different playing positions?** (e.g., quarterback, linebacker, wide receiver)\n",
      "* **Famous players or teams?** (Besides the 49ers, of course!)\n",
      "* **The history of the sport?**\n",
      "* **Football strategy and tactics?**\n",
      "* **The business side of football?** (e.g., salaries, contracts, team ownership)\n",
      "\n",
      "Let me know what piques your interest!\n"
     ]
    }
   ],
   "source": [
    "# Create a thread\n",
    "config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
    "\n",
    "# Start conversation\n",
    "input_message = HumanMessage(content=\"I would like to know about football.\")\n",
    "output = graph.invoke({\"messages\": [input_message]}, config)\n",
    "for m in output['messages'][-1:]:\n",
    "    m.pretty_print()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c0f3e842-4497-45e2-a924-69672a9bcb33",
   "metadata": {
    "id": "c0f3e842-4497-45e2-a924-69672a9bcb33"
   },
   "source": [
    "Let's confirm that our state is saved locally."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "cZoLBwonOMGa",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "cZoLBwonOMGa",
    "outputId": "b639e850-f8b6-4479-db17-9be352d1fa35"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Using summary: This is a summary of the conversation to date:\n",
      "\n",
      "Lance introduced himself twice, stating \"Hi! I'm Lance\" at the beginning and again later in the conversation.  He expressed his fondness for the San Francisco 49ers football team. The AI assistant correctly identified Lance's name and engaged him in a discussion about the 49ers, offering several avenues for conversation, including the team's history, roster, and memorable games.  The AI also attempted to guide the conversation towards specific aspects of the 49ers to move beyond general statements of fandom. Despite the AI's prompts, the conversation remained brief and somewhat repetitive, with Lance's repeated self-introductions and a lack of detailed engagement with the AI's suggestions regarding the 49ers.  The conversation demonstrates a successful initial identification of the user's name and interest, but highlights a challenge in moving the conversation beyond simple statements of preference.\n",
      "\n",
      "Message count: 6\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "Your name is Lance.\n"
     ]
    }
   ],
   "source": [
    "input_message = HumanMessage(content=\"what's my name?\")\n",
    "output = graph.invoke({\"messages\": [input_message]}, config)\n",
    "for m in output['messages'][-1:]:\n",
    "    m.pretty_print()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "oL7okbdYORfH",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "oL7okbdYORfH",
    "outputId": "8d195b9f-d15a-4218-c8e0-12d8a04660f5"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Using summary: This is a summary of the conversation to date:\n",
      "\n",
      "Lance introduced himself twice, stating \"Hi! I'm Lance\" at the beginning and again later in the conversation.  He expressed his fondness for the San Francisco 49ers football team. The AI assistant correctly identified Lance's name and engaged him in a discussion about the 49ers, offering several avenues for conversation, including the team's history, roster, and memorable games.  The AI also attempted to guide the conversation towards specific aspects of the 49ers to move beyond general statements of fandom. Despite the AI's prompts, the conversation remained brief and somewhat repetitive, with Lance's repeated self-introductions and a lack of detailed engagement with the AI's suggestions regarding the 49ers.  The conversation demonstrates a successful initial identification of the user's name and interest, but highlights a challenge in moving the conversation beyond simple statements of preference.\n",
      "\n",
      "Message count: 8\n",
      "Messages before summarizing: 8\n",
      "Existing summary: This is a summary of the conversation to date:\n",
      "\n",
      "Lance introduced himself twice, stating \"Hi! I'm Lance\" at the beginning and again later in the conversation.  He expressed his fondness for the San Francisco 49ers football team. The AI assistant correctly identified Lance's name and engaged him in a discussion about the 49ers, offering several avenues for conversation, including the team's history, roster, and memorable games.  The AI also attempted to guide the conversation towards specific aspects of the 49ers to move beyond general statements of fandom. Despite the AI's prompts, the conversation remained brief and somewhat repetitive, with Lance's repeated self-introductions and a lack of detailed engagement with the AI's suggestions regarding the 49ers.  The conversation demonstrates a successful initial identification of the user's name and interest, but highlights a challenge in moving the conversation beyond simple statements of preference.\n",
      "\n",
      "New summary: This is a summary of the conversation to date:\n",
      "\n",
      "Lance introduced himself twice, stating \"Hi! I'm Lance\" at the beginning and again later in the conversation. He expressed his fondness for the San Francisco 49ers football team. The AI assistant correctly identified Lance's name and engaged him in a discussion about the 49ers, offering several avenues for conversation, including the team's history, roster, and memorable games. The AI also attempted to guide the conversation towards specific aspects of the 49ers to move beyond general statements of fandom.  Despite the AI's prompts, the conversation remained brief and somewhat repetitive, with Lance's repeated self-introductions and a lack of detailed engagement with the AI's suggestions regarding the 49ers. The conversation demonstrates a successful initial identification of the user's name and interest, but highlights a challenge in moving the conversation beyond simple statements of preference.  This challenge persisted even after the AI directly asked Lance what he specifically enjoys about the 49ers, with Lance only reiterating his fandom without providing further details.  The conversation showcases the difficulty in engaging a user who provides minimal input beyond initial declarations of interest.\n",
      "\n",
      "Messages after truncation: 6\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "Great!  Many people are 49ers fans. To go beyond just liking them, what specifically about the 49ers do you enjoy the most?  Is it a certain player, their history, their recent performance, or something else?\n"
     ]
    }
   ],
   "source": [
    "input_message = HumanMessage(content=\"i like the 49ers!\")\n",
    "output = graph.invoke({\"messages\": [input_message]}, config)\n",
    "for m in output['messages'][-1:]:\n",
    "    m.pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "d2ab158a-5a82-417a-8841-730a4cc18ea7",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "d2ab158a-5a82-417a-8841-730a4cc18ea7",
    "outputId": "3d439d2c-a5c4-44e5-c3b9-0466ea89ecca"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "StateSnapshot(values={'messages': [HumanMessage(content='I would like to know about football.', additional_kwargs={}, response_metadata={}, id='7f17fab0-828a-4681-845a-7c2da11b03f4'), AIMessage(content=\"Okay, let's talk about football!  To help me focus our conversation, what specifically about football are you interested in?  For example, are you curious about:\\n\\n* **Specific rules of the game?**  (e.g.,  down and distance, penalties, scoring)\\n* **Different playing positions?** (e.g., quarterback, linebacker, wide receiver)\\n* **Famous players or teams?** (Besides the 49ers, of course!)\\n* **The history of the sport?**\\n* **Football strategy and tactics?**\\n* **The business side of football?** (e.g., salaries, contracts, team ownership)\\n\\nLet me know what piques your interest!\\n\", additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': []}, id='run-6c03bc80-e9c1-4e34-a794-e67c4cd43077-0', usage_metadata={'input_tokens': 352, 'output_tokens': 152, 'total_tokens': 504, 'input_token_details': {'cache_read': 0}})], 'summary': \"Here's an extended summary of the conversation:\\n\\nLance initially introduced himself twice, expressing his fondness for the San Francisco 49ers. The AI assistant acknowledged his name and offered various discussion points related to the 49ers, demonstrating a willingness to engage in a conversation about the team.  However, Lance instead shifted the focus to a broader interest in football itself.  The conversation then transitioned away from the 49ers, with Lance expressing a desire to learn more about football in general. The AI assistant responded by offering several specific areas within the sport that could be discussed, such as rules, positions, famous players, history, strategy, or the business aspects of the game.  The conversation ended with the AI ready to delve into one of these areas, demonstrating adaptability to Lance's changing interests.  The overall interaction showed a willingness from both participants to engage in conversation, but the initial focus on the 49ers was quickly superseded by a broader interest in the sport of football.\\n\"}, next=(), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1efa99d2-2c5c-6b61-8015-3dc2d6bcc460'}}, metadata={'source': 'loop', 'writes': {'summarize_conversation': {'messages': [RemoveMessage(content='', additional_kwargs={}, response_metadata={}, id='086e80ce-8bd0-4dee-b13c-f47160d6755f'), RemoveMessage(content='', additional_kwargs={}, response_metadata={}, id='run-7f8af5e1-5248-4ccd-ab46-87580422f0d6-0'), RemoveMessage(content='', additional_kwargs={}, response_metadata={}, id='e33f42ee-cdb0-45ec-b100-54dcd4c82e71'), RemoveMessage(content='', additional_kwargs={}, response_metadata={}, id='run-4152603e-5c5f-4a26-be8c-2b2bab373e75-0'), RemoveMessage(content='', additional_kwargs={}, response_metadata={}, id='85513182-bfb9-445b-b6ff-a6553bf63767'), RemoveMessage(content='', additional_kwargs={}, response_metadata={}, id='run-68b784a6-c518-4904-a208-6214b7223414-0')], 'summary': \"Here's an extended summary of the conversation:\\n\\nLance initially introduced himself twice, expressing his fondness for the San Francisco 49ers. The AI assistant acknowledged his name and offered various discussion points related to the 49ers, demonstrating a willingness to engage in a conversation about the team.  However, Lance instead shifted the focus to a broader interest in football itself.  The conversation then transitioned away from the 49ers, with Lance expressing a desire to learn more about football in general. The AI assistant responded by offering several specific areas within the sport that could be discussed, such as rules, positions, famous players, history, strategy, or the business aspects of the game.  The conversation ended with the AI ready to delve into one of these areas, demonstrating adaptability to Lance's changing interests.  The overall interaction showed a willingness from both participants to engage in conversation, but the initial focus on the 49ers was quickly superseded by a broader interest in the sport of football.\\n\"}}, 'thread_id': '1', 'step': 21, 'parents': {}}, created_at='2024-11-23T13:16:23.012211+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1efa99d2-1d76-63de-8014-6dcff477bde8'}}, tasks=())"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
    "graph_state = graph.get_state(config)\n",
    "graph_state"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "Qekd1ttftUoj",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "Qekd1ttftUoj",
    "outputId": "297de343-0260-4b2a-b617-72c103ebe5dc"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
    "messages = graph.get_state(config).values.get(\"messages\")\n",
    "len(messages)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "de-rftSAKYqD",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 182
    },
    "id": "de-rftSAKYqD",
    "outputId": "ab0f2b34-ec54-41b7-c622-5732b2fd31ea"
   },
   "outputs": [
    {
     "data": {
      "application/vnd.google.colaboratory.intrinsic+json": {
       "type": "string"
      },
      "text/plain": [
       "\"Here's an extended summary of the conversation:\\n\\nLance initially introduced himself twice, expressing his fondness for the San Francisco 49ers. The AI assistant acknowledged his name and offered various discussion points related to the 49ers, demonstrating a willingness to engage in a conversation about the team.  However, Lance instead shifted the focus to a broader interest in football itself.  The conversation then transitioned away from the 49ers, with Lance expressing a desire to learn more about football in general. The AI assistant responded by offering several specific areas within the sport that could be discussed, such as rules, positions, famous players, history, strategy, or the business aspects of the game.  The conversation ended with the AI ready to delve into one of these areas, demonstrating adaptability to Lance's changing interests.  The overall interaction showed a willingness from both participants to engage in conversation, but the initial focus on the 49ers was quickly superseded by a broader interest in the sport of football.\\n\""
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph_state = graph.get_state(config).values.get(\"summary\",\"\")\n",
    "graph_state"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "BRgmis1C8S7e",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "BRgmis1C8S7e",
    "outputId": "a4960edb-e632-4969-9c3a-b6198344107f"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ls: cannot access 'state_db': No such file or directory\n"
     ]
    }
   ],
   "source": [
    "!ls state_db\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "XdbM-0ReTQja",
   "metadata": {
    "id": "XdbM-0ReTQja"
   },
   "source": [
    "Download required db files"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "V1CcFo3H2WWL",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 17
    },
    "id": "V1CcFo3H2WWL",
    "outputId": "67ea28bb-1a62-4f4a-b717-71871f353e06"
   },
   "outputs": [
    {
     "data": {
      "application/javascript": "\n    async function download(id, filename, size) {\n      if (!google.colab.kernel.accessAllowed) {\n        return;\n      }\n      const div = document.createElement('div');\n      const label = document.createElement('label');\n      label.textContent = `Downloading \"${filename}\": `;\n      div.appendChild(label);\n      const progress = document.createElement('progress');\n      progress.max = size;\n      div.appendChild(progress);\n      document.body.appendChild(div);\n\n      const buffers = [];\n      let downloaded = 0;\n\n      const channel = await google.colab.kernel.comms.open(id);\n      // Send a message to notify the kernel that we're ready.\n      channel.send({})\n\n      for await (const message of channel.messages) {\n        // Send a message to notify the kernel that we're ready.\n        channel.send({})\n        if (message.buffers) {\n          for (const buffer of message.buffers) {\n            buffers.push(buffer);\n            downloaded += buffer.byteLength;\n            progress.value = downloaded;\n          }\n        }\n      }\n      const blob = new Blob(buffers, {type: 'application/binary'});\n      const a = document.createElement('a');\n      a.href = window.URL.createObjectURL(blob);\n      a.download = filename;\n      div.appendChild(a);\n      a.click();\n      div.remove();\n    }\n  ",
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/javascript": "download(\"download_add7d838-8fdc-4c80-85cd-f84c23c394b8\", \"example.db\", 110592)",
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/javascript": "\n    async function download(id, filename, size) {\n      if (!google.colab.kernel.accessAllowed) {\n        return;\n      }\n      const div = document.createElement('div');\n      const label = document.createElement('label');\n      label.textContent = `Downloading \"${filename}\": `;\n      div.appendChild(label);\n      const progress = document.createElement('progress');\n      progress.max = size;\n      div.appendChild(progress);\n      document.body.appendChild(div);\n\n      const buffers = [];\n      let downloaded = 0;\n\n      const channel = await google.colab.kernel.comms.open(id);\n      // Send a message to notify the kernel that we're ready.\n      channel.send({})\n\n      for await (const message of channel.messages) {\n        // Send a message to notify the kernel that we're ready.\n        channel.send({})\n        if (message.buffers) {\n          for (const buffer of message.buffers) {\n            buffers.push(buffer);\n            downloaded += buffer.byteLength;\n            progress.value = downloaded;\n          }\n        }\n      }\n      const blob = new Blob(buffers, {type: 'application/binary'});\n      const a = document.createElement('a');\n      a.href = window.URL.createObjectURL(blob);\n      a.download = filename;\n      div.appendChild(a);\n      a.click();\n      div.remove();\n    }\n  ",
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/javascript": "download(\"download_5f03640a-9d7b-423b-87f6-c130a704c502\", \"example.db-wal\", 111272)",
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from google.colab import files\n",
    "files.download('state_db/example.db')\n",
    "files.download('state_db/example.db-wal')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1e21152d-ed9c-408d-b7d5-f634c9ce81e2",
   "metadata": {
    "id": "1e21152d-ed9c-408d-b7d5-f634c9ce81e2"
   },
   "source": [
    "### Persisting state\n",
    "\n",
    "Using database like Sqlite means state is persisted!\n",
    "\n",
    "For example, we can re-start the notebook kernel and see that we can still load from Sqlite DB on disk.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8466e418-1a46-4cdb-a51a-6ae14281bb85",
   "metadata": {
    "id": "8466e418-1a46-4cdb-a51a-6ae14281bb85"
   },
   "source": [
    "## LangGraph Studio\n",
    "\n",
    "--\n",
    "\n",
    "**⚠️ DISCLAIMER**\n",
    "\n",
    "*Running Studio currently requires a Mac. If you are not using a Mac, then skip this step.*\n",
    "\n",
    "--\n",
    "\n",
    "Now that we better understand external memory, recall that the LangGraph API packages your code and provides you with with built-in persistence.\n",
    "\n",
    "And the API is the back-end for Studio!\n",
    "\n",
    "Load the `chatbot` in the UI, which uses `module2-/studio/chatbot.py` set in `module2-/studio/langgraph.json`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c4916d8b",
   "metadata": {
    "id": "c4916d8b"
   },
   "source": []
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "gpuType": "T4",
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
